//: [Previous](@previous)
//: ## 泛型 (Generics)
// 泛型可以将类型参数化, 提高代码复用率, 减少代码量
//func swapValue1(_ a: inout Int, _ b: inout Int){ //这个是普通的交换函数,这里定义死了是Int类型
//    (a, b) = (b, a)
//}
func swapValue2<T>(_ a: inout T, _ b: inout T) {//在声明加上了<T>, 后面的参数也直接用T, 这样子就是泛型
    (a, b) = (b, a)
}

var i1 = 10
var i2 = 20
swapValue2(&i1, &i2)
print("i1:\(i1), i2:\(i2)")
//i1:20, i2:10

struct Date {
    var year = 0, month = 0, day = 0
}
var date1 = Date(year: 2020, month: 20, day: 20)
var date2 = Date(year: 4040, month: 40, day: 40)
swapValue2(&date1, &date2)
print("date1:\(date1), date2:\(date2)")
//date1:Date(year: 4040, month: 40, day: 40), date2:Date(year: 2020, month: 20, day: 20)

// 将泛型函数 赋值给  变量
// sum1 是普通的将函数赋值给变量
func sum1(_ a: Int, _ b: Int) -> Int { a+b }
var fn1 = sum1
fn1(10 , 20) // 30
// 泛型函数赋值给变量,需要在变量的声明处先确定好类型,才可以使用
var fn2: (inout Int, inout Int) -> () = swapValue2
fn2(&i1, &i2)

// 泛型函数中的T,如何才能表达多种类型同时使用
func swapValue3<T1, T2, T3>(_ a: inout T1, _ b: inout T2, _ c: inout T3) {
//    (a, b, c) = (c, b, a)
}
//对应的变量来接收函数的写法👇🏻
var fn3: (inout Int, inout Float, inout Double) -> () = swapValue3
//举例2(没有inout的正常情况下)
func test1<T1, T2>(_ t1: T1, _ t2: T2) {}
var fn4: (Int, Double) -> () = test1
//: ## 泛型类型
// 假设有这么个需求,用个类来表示栈,栈的特点就是先进后出
class Stack<E> {//栈
    var elements = [E]() //用数组表示栈里面的元素
    
    func push(_ element: E) { //放新的元素进栈
        elements.append(element)
    }
    
    func pop() -> E { //取走最后一个元素,模拟栈的后进先出
        elements.removeLast()
    }
    
    func top() -> E { //查看栈的最后一个元素, 模拟查看栈的现在最顶的元素
        elements.last!
    }
    
    func size() -> Int { //查看栈的元素个数, 所以肯定返回是Int
        elements.count
    }
    
//    init(firstElement: E){ // 修改初始器,达到传入第一个元素让系统知道这个泛型是哪种类型,可以自动创建这个类型的内存的作用
//        elements.append(E)
//    }
}

var intStack = Stack<Int>() //这里创建的时候需要标明Int类型,是因为现在是没有传入任何参数的,系统不知道这次的泛型是要转成什么类型,就不知道应该怎么初始化对应的内存空间,所以需要提前说明
var stringStack = Stack<String>()
var anyStack = Stack<Any>() //也可以使用any代表任何类型
//var firstStack = Stack(firstElement: 10.0) 这种就是修改了初始化器,在初始化的时候已经传入了第一个参数,这样子系统就会知道这个泛型初始化的时候根据第一个参数的类型去初始化,就不用我们自己标明

// 泛型继承的写法: 子类的声明加上<X>
class SubStack<E> : Stack<E> {}

// 泛型的 结构体
struct Stack1<E> {
    var elements = [E]()
    
    // 结构体和类最大的区别就是因为结构体的值类型, 在函数内部需要改变自身内存的时候, 需要加上 mutating
    mutating func push(_ element: E) {
        elements.append(element)
    }
    mutating func pop() -> E {
        elements.removeLast()
    }
    func top() -> E {
        elements.last!
    }
    func size() -> Int {        elements.count
    }
}

// 泛型的 枚举
enum Score<T> { //举例一个分数的枚举,有具体分数,和ABC评价
    case point(T)
    case grade(String)
}
let score1 = Score<Int>.point(100)
let score2 = Score.point(100)
// 1和2其实是同效果的, 2已经在初始化的时候就传入了int100, 所以系统会给T自动转成Int类型
let score3 = Score<Int>.grade("A") // 这里要注意📢: 虽然这行是想用到.grade是跟String, 但是假如直接初始化的时候Score.grade("A"), 这样子虽然.grade有了内容, 但是point(T)没有啊, 就会造成系统不知道T是要给他什么类型, 这样子初始化的时候就不知道怎么分配空间. 所以在和泛型有关的初始声明中, 一定要确保<T>的类型准确性, 不然系统会初始化失败不知道分配多少内存
//: ## 关联类型(Associated Type)
/*
 关联类型的作用: 给协议中用到的类型定义一个占位名称
 协议中可以使用多个关联类型
 其实关联联系就是泛型的意思(可以完全理解成泛型), 只是协议不能使用泛型(泛型只能枚举结构体类可以使用), 所以搞了个关联类型给协议以达到同样的效果
 */
protocol Stackable { //还是关于栈这个需求,用协议来实现
    associatedtype Element // 这就是关联类型, 其实就是泛型<T>
//    associatedtype Element2 //可以写多个关联类型, 类似泛型的T1 T2 T3
    mutating func push(_ element: Element) //写有mutating是为了预埋有可能未来有结构体使用
    mutating func pop() -> Element
    func top() -> Element
    func size() -> Int
}
/*
 正常其他类型的泛型应该是👇🏻这样子实现, 只是协议特殊, 就只能用👆🏻专用的关联类型
 protocol Stackable<T> {
     mutating func push(_ element: T)
 }
 */
class StringStack1 : Stackable {
//    typealias Element = String 这行代码是为了告诉系统这次的泛型用的是哪种类型. 但是也可以直接在下面的代码中用类型直接代替Element,这样子系统也可以直接知道了
    var elements = [String]()
    func push(_ element: String) { //这里直接写了String, 其实就也是直接告诉了系统这次的泛型使用的是String
        elements.append(element)
    }
    func pop() -> String {
        elements.removeLast()
    }
    func top() -> String {
        elements.last!
    }
    
    func size() -> Int {
        elements.count
    }
}

// 另外一直继承的写法, 硬要写成泛型, 和上门👆🏻的区别其实就是String 硬写成了 E
class Stack2<E> : Stackable { //正常写泛型,后面加上协议
//   typealias Element = E 老规矩,这句话其实可以不写
    
    var elements = [E]()
    func push(_ element: E) { //这里直接写了String, 其实就也是直接告诉了系统这次的泛型使用的是String
        elements.append(element)
    }
    func pop() -> E {
        elements.removeLast()
    }
    func top() -> E {
        elements.last!
    }
    
    func size() -> Int {
        elements.count
    }
}
//:## 类型约束 (可以理解成泛型的约束,允许什么类型进来)
protocol Runnable {}
class Person {}
func swapValues<T : Person & Runnable>(_ a: inout T, _ b: inout T){
    (a, b) = (b, a)
}// <T : Person & Runnable> 是表示T只能允许 满足协议Runnable 并且 是Person类或者是它的子类, 同时满足的类型才可以传进来
//协议的类型约束
protocol Stackable3 {
    //Equatable是系统自带的一个协议
    associatedtype Element : Equatable
}
class Stack3 : Stackable3 {
    typealias Element = Int
}
//👆🏻 👇🏻 2个写法都可以, 👆🏻是在实现里面标明遵循的协议的泛型Element=Int, 👇🏻是在声明处用<E>去继承了协议的泛型所遵循的Equatable协议,这样子Stack4在初始化实现的时候就可以和正常的泛型一样E=Int去使用了
class Stack4<E : Equatable> : Stackable3 {
    typealias Element = E
}
/*
 解析👇🏻函数
 1. <S1: Stackable3, S2: Stackable3> : 2个泛型, 分别需要满足Stackable3的约束
 2. (_ s1: S1, _ s2: S2) : 2个参数, s1是属于S1类型, s2是属于S2类型
 3. where S1.Element == S2.Element, S1.Element : Hashable : where后面是 泛型的关联类型的一些更复杂的限定条件
  3-1. S1.Element == S2.Element: 泛型S1的关联值要等于S2的关联值, 其实就是S1和S2的类型要等于
  3-2.  S1.Element : Hashable : 泛型S1的关联值要满足协议Hashable
 */
func equal<S1: Stackable3, S2: Stackable3>(_ s1: S1, _ s2: S2) -> Bool 
where S1.Element == S2.Element, S1.Element : Hashable
{//Hashable是系统自带的一个协议
    return false
}
var s1 = Stack4<Int>()
var s2 = Stack4<Int>()
var s3 = Stack4<String>()
equal(s1, s2)
//equal(s1, s3) 不满足S1.Element == S2.Element
//:## 协议类型的注意点
protocol Runnable1 {
    associatedtype Speed //(Speed中文是速度)协议中有关联类型,
    var speed : Speed { get } //规定有速度这个属性(只读)
}
class Person1 : Runnable1 {
    var speed: Double { 0.0 } //实现了协议的属性, 自己定义为Double
}
class Car1 : Runnable1 {
    var speed: Int { 0 } //实现了协议的属性, 自己定义为Int
}
/*
 //这是在协议没有关联类型associatedtype的情况下可以运行的代码, 但是假如协议加上了associatedtype后, 这个代码就运行不成功了, 会报错系统不知道这个泛型指的是什么,var r1 = get(0)和var r2 = get(1)的时候, 假如初始化出来了Runnable1类型可以成功, 但是Runnable1里面有属性Speed是泛型啊, 泛型是必须在初始化的时候就要明确类型让系统分配出对应类型的空间, 虽然系统知道r1=Person1(),但是r1里面的属性speed是什么类型,在初始化的这一刻是还不知道的啊, 所以会初始化失败
 func get(_ type: Int) -> Runnable1 { //根据参数,返回Runnable1类型
     if type == 0 {
         return Person1()
     }
     return Car1()
 }// 因为Person1和Car1都是继承自Runnable1,所以可以直接当返回值
 var r1 = get(0)
 var r2 = get(1)
 */

// 解决方法1: 使用泛型
func get1<T : Runnable1>(_ type: Int) -> T {
    if type == 0 {
        return Person1() as! T
    }
    return Car1() as! T
}
var r1: Person1 = get1(0)
var r2: Car1 = get1(1)
/*
 var r1: Person1 = get1(0) : 在初始化的时候就规定了泛型T是Person1, 那么初始化的时候因为已经是Person1, 那么属性的speed就是Double了,这样子所有类型都明确了,所以可以成功. 其实就是相当于把 var r1: Person1 中的Person1 去替换了 get1方法的T,就不是泛型,而是一个Person1的方法
 */
//:## 不透明类型(Opaque Type)
// 解决方法2: 使用some关键字声明一个不透明类型,不对外公开 some的作用 :限制只能返回一种类型
func get2(_ type: Int) -> some Runnable1 {
    return Car1()
}
var v3 = get2(0)
/*
 get2方法:
 因为返回值加上了 some ,所以实现里面只能返回一种类型, 就不能有返回Person的可能.
 这个some此时的好处: 这样子强制用Runnable1作为返回值,var v3 的v3在外界就是Runnable1类型, 世人只知道v3是遵守Runnable1协议的, 不知道我Runnable1里面有什么内容, 这就是不透明类型的含义, 就是为了对外屏蔽内部的真实信息
 var v3 = get2(0) : 此时v3虽然实际是car类型, 但是因为不透明类型,现在对外只是一个遵守Runnable1协议的对象,没人知道它是car类型,所以只能调用Runnable1协议的speed,不能使用car的自己里面的一些方法属性
 */
//:## some
// some 除了用在返回值类型上, 一般还可以用在属性类型上
protocol Runnable2 {associatedtype Speed } //创建一个协议
class Dog : Runnable2 { typealias Speed = Double } //创建一个类 狗,遵循协议,把协议的关联类型(泛型)定义成Double
class Person2 {//人,永远一条狗,但是不希望被人知道是狗,只能让人知道是一个遵循Runnable2协议的宠物
    var pet: some Runnable2 {//返回值是Runnable2,Dog遵循了Runnable2协议,所以可以是返回值
        return Dog()
    }
}







//: [Next](@next)
